/*
 * Copyright (c) 2022 PATEO CONNECT+ (Nanjing) Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "wakeup_manager.h"
#include "voice_assistant_log.h"
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <thread>
#include <time.h>
#include <unistd.h>

#define RATE 16000
#define DURATION_MS 30 // 10, 20 ,30ms
#define CHUNK_SIZE (RATE * DURATION_MS / 1000)
#define BUFFER_LENGTH CHUNK_SIZE
#define MIN_DECODE_LENGTH BUFFER_LENGTH * 40 //最小识别的长度1200ms
#define MAX_DECODE_LENGTH BUFFER_LENGTH * 100 //最大识别的长度3000ms

#define BUFFER_FILE_DIR "/data/asr"
#define BUFFER_FILE_PATH BUFFER_FILE_DIR "/wakeup_tmp.wav"

namespace OHOS {
namespace CarVoiceAssistant {
    WakeUpManager::WakeUpManager()
    {
        status_ = WakeUpStatusNotInit;
        lastVadResult_ = 0;
        file_ = nullptr;
        needClearBeforeProcess_ = false;

        pVad_ = nullptr;
        decoder_ = nullptr;
        config_ = nullptr;
    }

    WakeUpManager::~WakeUpManager()
    {
        if (pVad_ != nullptr) {
            WebRtcVad_Free(pVad_);
        }
        if (config_ != nullptr) {
            cmd_ln_free_r(config_);
        }
        if (decoder_ != nullptr) {
            ps_free(decoder_);
        }

        if (file_ != nullptr) {
            fclose(file_);
        }
    }

    void WakeUpManager::Init()
    {
        std::thread thread(&WakeUpManager::RunInit, this);
        thread.detach();
    }

    void WakeUpManager::RunInit()
    {
        pVad_ = WebRtcVad_Create();
        if (pVad_ == NULL) {
            VOICE_ASSISTANT_LOGE("WebRtcVad_Create failed");
            return;
        }

        if (WebRtcVad_Init(pVad_)) {
            VOICE_ASSISTANT_LOGE("WebRtcVad_Init failed");
            return;
        }

        if (WebRtcVad_set_mode(pVad_, 3)) { // 0-3 越大越粗略，连续静音多
            VOICE_ASSISTANT_LOGE("WebRtcVad_set_mode failed");
            return;
        }
        VOICE_ASSISTANT_LOGI("--------init vad success--------");

        VOICE_ASSISTANT_LOGI("--------init pocketsphinx--------");
        if ((config_ = cmd_ln_init(NULL, ps_args(), TRUE,
                 "-hmm", "/system/etc/pocketsphinx/zh/zh",
                 "-lm", "/system/etc/pocketsphinx/zh/zh_cn.lm.bin",
                 "-dict", "/system/etc/pocketsphinx/zh/zh_cn.dic",
                 NULL))
            == NULL)
            VOICE_ASSISTANT_LOGE("Command line parse failed");
        if ((decoder_ = ps_init(config_)) == NULL)
            VOICE_ASSISTANT_LOGE("PocketSphinx decoder init failed");

        VOICE_ASSISTANT_LOGI("--------init pocketsphinx success--------");

        recursive_mutex_.lock();
        status_ = WakeUpStatusInitilazed;
        recursive_mutex_.unlock();
    }

    void WakeUpManager::Process(void* data, size_t length)
    {
        recursive_mutex_.lock();

        if (status_ != WakeUpStatusInitilazed) {
            recursive_mutex_.unlock();
            return;
        }

        size_t vadLength = BUFFER_LENGTH;
        if (length < BUFFER_LENGTH) {
            VOICE_ASSISTANT_LOGI("WakeUpManager::Process: length is too short : length:%{public}zu, vadLength:%{public}zu", length, vadLength);
            ClearState();
            recursive_mutex_.unlock();
            return;
        }

        if (needClearBeforeProcess_) {
            ClearState();
        }

        int nRet = WebRtcVad_Process(pVad_, 16000, static_cast<int16_t*>(data), vadLength);
        // VOICE_ASSISTANT_LOGI("WakeUpManager::Process: ret:%{public}d, length:%{public}zu", nRet, length);

        if (lastVadResult_ == 0 && nRet == 1) { //开始有声音
            VOICE_ASSISTANT_LOGI("WakeUpManager::Process: file reset");

            if (file_ == nullptr) {
                VOICE_ASSISTANT_LOGI("WakeUpManager::Process: file fopen");
                if (access(BUFFER_FILE_DIR, F_OK) != 0) {
                    VOICE_ASSISTANT_LOGI("WakeUpManager::Process: create dir:%{public}s", BUFFER_FILE_DIR);
                    mkdir(BUFFER_FILE_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
                }
                file_ = fopen(BUFFER_FILE_PATH, "w+");
                if (file_ == nullptr) {
                    VOICE_ASSISTANT_LOGI("WakeUpManager::Process: fopen failed");
                    ClearState();
                    recursive_mutex_.unlock();
                    return;
                }
            }

            ftruncate(fileno(file_), 0);
            fseek(file_, 0, SEEK_SET);

            fwrite(data, length, 1, file_);
        } else if (lastVadResult_ == 1 && nRet == 1) {
            // VOICE_ASSISTANT_LOGI("WakeUpManager::Process: fwrite");
            fwrite(data, length, 1, file_);
        } else if (lastVadResult_ == 1 && nRet == 0) {
            fseek(file_, 0, SEEK_END);
            long fileLength = ftell(file_);

            if (fileLength < MIN_DECODE_LENGTH || fileLength > MAX_DECODE_LENGTH) {
                VOICE_ASSISTANT_LOGI("WakeUpManager::Process: judge process too short or too long:%{public}ld", fileLength);
                ClearState();
                recursive_mutex_.unlock();
                return;
            } else {
                VOICE_ASSISTANT_LOGI("WakeUpManager::Process: judge process: %{public}ld", fileLength);
                status_ = WakeUpStatusRecognizing;
                std::thread thread(&WakeUpManager::RunDecode, this, file_);
                thread.detach();
            }
        }
        lastVadResult_ = nRet;
        recursive_mutex_.unlock();
    }

    void WakeUpManager::RunDecode(FILE* fp)
    {
        VOICE_ASSISTANT_LOGI("WakeUpManager:RunDecode start");

        fseek(fp, 0, SEEK_SET);
        ps_decode_raw(decoder_, fp, -1);

        int32 score;
        const char* outstr = ps_get_hyp(decoder_, &score);
        if (outstr != NULL) {
            VOICE_ASSISTANT_LOGI("WakeUpManager Recognized: %{public}s,  score = %{public}d", outstr, score);
            if (callback_ != nullptr && score > -3300) {
                callback_->WakeUpCallback(outstr);
            }
            ClearState();
            return;
        } else {
            VOICE_ASSISTANT_LOGI("WakeUpManager ps_get_hyp failed");
            ClearState();
            return;
        }
    }

    void WakeUpManager::ClearState()
    {
        recursive_mutex_.lock();
        VOICE_ASSISTANT_LOGI("WakeUpManager ClearState");

        lastVadResult_ = 0;
        if (status_ == WakeUpStatusRecognizing) {
            status_ = WakeUpStatusInitilazed;
        }
        needClearBeforeProcess_ = false;
        recursive_mutex_.unlock();
    }

    void WakeUpManager::SetCallback(wptr<IWakeUpCallback> callback)
    {
        callback_ = callback;
    }

    void WakeUpManager::SetNeedClearBeforeProcess()
    {
        needClearBeforeProcess_ = true;
    }
}
}